﻿using Marvin.Cache.Headers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using RuanMou.Projects.SeckillServices.Dtos;
using RuanMou.Projects.SeckillServices.Models;
using RuanMou.Projects.SeckillServices.Services;
using RuanMou.Projects.SeckillServices.Vos;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace RuanMou.Projects.SeckillServices.Controllers
{
    /// <summary>
    /// 秒杀服务控制器
    /// </summary>
    [Route("Seckills")]
    [ApiController]
    public class SeckillsController : ControllerBase
    {
        private readonly ISeckillService SeckillService;
        private readonly IMemoryCache memoryCache;// 本地缓存
        private readonly IDistributedCache distributedCache; // 分布式缓存

        private static Processor p = new Processor();
        public SeckillsController(ISeckillService SeckillService
            , IMemoryCache memoryCache
            , IDistributedCache distributedCache)
        {
            this.SeckillService = SeckillService;
            this.memoryCache = memoryCache;
            this.distributedCache = distributedCache;
        }

        // GET: api/Seckills 获取所有秒杀商品
        [HttpGet]
        [HttpCacheExpiration(NoStore=false, MaxAge=10000)]
        public ActionResult<IEnumerable<Seckill>> GetSeckills()
        {
            // 1、业务信息
            Console.WriteLine("查询秒杀商品信息");
            // 2、查询数据库
            List<Seckill> seckills = SeckillService.GetSeckills().ToList();
            //设置响应头
            //HttpContext.Response.Headers.Add("cache-control", "max-age=60,public");
            //HttpContext.Response.Headers.Add("etag", "5c20abbd-e2e8");
            //HttpContext.Response.Headers.Add("last-modified", "Mon, 24 Dec 2022 09:49:49 GMT");

            #region 1、本地缓存
            {
                /* // 1、先查询缓存
                 List<Seckill> seckills = memoryCache.Get<List<Seckill>>("seckills");
                 if (seckills == null)
                 {
                     // 1.1 查询数据库
                     seckills = SeckillService.GetSeckills().ToList();
                     // 1.2 添加到缓存
                     MemoryCacheEntryOptions memoryCacheEntryOptions = new MemoryCacheEntryOptions();
                     // 1、过期时间(保证数据和源头一致)
                     memoryCacheEntryOptions.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3);
                     // 2、设置缓存量大小。(保证系统不影响其他操作)
                     memoryCacheEntryOptions.SetSize(1024);
                     // 3、方案：使用异步更新。
                     memoryCacheEntryOptions.RegisterPostEvictionCallback((key,value,reason,state) =>{
                         // 1、key = seckills
                         // 2、value = List<Seckill>
                         // 3、reason = 回收的原因
                         // 4、state = 是否成功，是否失败
                         Console.WriteLine($"缓存自动失效：key:{key},value:{value},reason:{reason},state:{state}");

                         // 1、查询数据库的代码。
                         // 自动同步：
                         // 1、根据key查询数据库，查询秒杀商品数据
                         // 2、然后把查询秒杀商品数据存储到数据库中。
                         // 扩展：数据异构形式。
                     });
                     // 4、如果数据量缓存数据量，比较大。1000条数据
                     //    都会设置过期时间。

                     // 5、优先级设置。
                     memoryCacheEntryOptions.Priority = CacheItemPriority.High;

                     memoryCache.Set<List<Seckill>>("seckills", seckills, memoryCacheEntryOptions);
                 }
                 return seckills;*/
            }
            #endregion

            #region 2、本地缓存-安全
            {
               /* Console.WriteLine("获取秒杀数据");
                List<Seckill> seckills = null;
                bool flag = memoryCache.TryGetValue<List<Seckill>>("seckills", out seckills);
                if (!flag)
                {
                    // 1.1 查询数据库
                    seckills = SeckillService.GetSeckills().ToList();
                    // 1.2 添加到缓存
                    MemoryCacheEntryOptions memoryCacheEntryOptions = new MemoryCacheEntryOptions();
                    // 1、过期时间(保证数据和源头一致)
                    memoryCacheEntryOptions.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3);
                    // 2、设置缓存量大小。(保证系统不影响其他操作)
                    memoryCacheEntryOptions.SetSize(1024);
                    // 3、方案：使用异步更新。
                    memoryCacheEntryOptions.RegisterPostEvictionCallback((key, value, reason, state) => {
                        // 1、key = seckills
                        // 2、value = List<Seckill>
                        // 3、reason = 回收的原因
                        // 4、state = 是否成功，是否失败
                        Console.WriteLine($"缓存自动失效：key:{key},value:{value},reason:{reason},state:{state}");

                        // 1、查询数据库的代码。
                        // 自动同步：
                        // 1、根据key查询数据库，查询秒杀商品数据
                        // 2、然后把查询秒杀商品数据存储到数据库中。
                        // 扩展：数据异构形式。
                    });
                    // 4、如果数据量缓存数据量，比较大。1000条数据
                    //    都会设置过期时间。

                    // 5、优先级设置。
                    memoryCacheEntryOptions.Priority = CacheItemPriority.High;

                    memoryCache.Set<List<Seckill>>("seckills", seckills, memoryCacheEntryOptions);
                }
                return seckills;*/
            }
            #endregion

            #region 3、分布式缓存
            {
                /*Console.WriteLine("获取秒杀数据");
                // 1、先查询redis分布式缓存
                string seckillsString = distributedCache.GetString("seckills");
                List<Seckill> seckills = null;
                if (string.IsNullOrEmpty(seckillsString))
                {
                    // 1.1 查询数据库
                    seckills = SeckillService.GetSeckills().ToList();
                    seckillsString = JsonConvert.SerializeObject(seckills);

                    // 设置过期的时间。
                    var distributedCacheEntryOptions = new DistributedCacheEntryOptions();
                    distributedCacheEntryOptions.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(3);
                    // 1.2 存储到redis中
                    distributedCache.SetString("seckills", seckillsString, distributedCacheEntryOptions);
                }

                // 1.3反序列化成对象
                seckills = JsonConvert.DeserializeObject<List<Seckill>>(seckillsString);
                return seckills;*/
            }
            #endregion

            //HttpCLient
            //  HttpContext.Response.Headers.Add("cache-control", "max-age=60,public");
            // HttpContext.Response.Headers.Add("etag","5c20abbd-e2e8");
            // HttpContext.Response.Headers.Add("last-modified", "Mon, 24 Dec 2022 09:49:49 GMT");

           // HttpContext.Response.Headers.Add("vary", "Accept-Encoding");

            // 会执行很长时间
            // p.ProcessTransaction(new Customer(Guid.NewGuid().ToString()));
            return seckills;
        }

        /// <summary>
        /// 根据时间查询
        /// </summary>
        /// <param name="seckill"></param>
        /// <returns></returns>
        [HttpGet("GetList")]
        public ActionResult<IEnumerable<Seckill>> GetList([FromQuery]Seckill seckill)
        {
            List<Seckill> seckills = SeckillService.GetSeckills(seckill).ToList();

            #region 本地缓存
            {
                /*Console.WriteLine("获取秒杀数据");
                // 1、查询本地缓存
                List<Seckill> seckills = memoryCache.Get<List<Seckill>>("seckills");
                if (seckills == null)
                {
                    // 1.1 查询数据库
                    seckills = SeckillService.GetSeckills().ToList();

                    // 1.2 添加到缓存
                    memoryCache.Set<List<Seckill>>("seckills", seckills);
                }
                return seckills;*/
            }
            #endregion

            #region 分布式缓存
            {
               /* Console.WriteLine("获取秒杀数据");
                // 1、先查询redis分布式缓存
                string seckillsString = distributedCache.GetString("seckills");
                List<Seckill> seckills = null;
                if (string.IsNullOrEmpty(seckillsString))
                {
                    // 1.1 查询数据库
                    seckills = SeckillService.GetSeckills().ToList();
                    seckillsString = JsonConvert.SerializeObject(seckills);

                    // 1.2 存储到redis中
                    distributedCache.SetString("seckills", seckillsString);
                }
                // 1.3反序列化成对象
                seckills = JsonConvert.DeserializeObject<List<Seckill>>(seckillsString);
                return seckills;*/
            }
            #endregion

            #region 分布式缓存 + 本地缓存
            {
                // 1、先查询redis分布式缓存
               /* string seckillsString = distributedCache.GetString("seckills");
                List<Seckill> seckills = null;
                if (string.IsNullOrEmpty(seckillsString))
                {
                    //2、查询本地缓存
                    seckills = memoryCache.Get<List<Seckill>>("seckills");
                    if (seckills == null)
                    {
                        // 2.1 查询数据库
                        seckills = SeckillService.GetSeckills().ToList();

                        // 2.2 添加到缓存
                        memoryCache.Set<List<Seckill>>("seckills", seckills);
                    }
                    // 1.2对象json序列化
                    seckillsString = JsonConvert.SerializeObject(seckills);

                    // 1.3 存储到redis中
                    distributedCache.SetString("seckills", seckillsString);
                }
                // 1.4反序列化成对象
                seckills = JsonConvert.DeserializeObject<List<Seckill>>(seckillsString);
                return seckills;*/
            }
            #endregion
            return seckills;
        }

        // GET: api/Seckills/5
        [HttpGet("{id}")]
        public ActionResult<Seckill> GetSeckill(int id)
        {
            var Seckill = SeckillService.GetSeckillById(id);

            if (Seckill == null)
            {
                return NotFound();
            }

            return Seckill;
        }

        // PUT: api/Seckills/5
        // To protect from overposting attacks, enable the specific properties you want to bind to, for
        // more details, see https://go.microsoft.com/fwlink/?linkid=2123754.
        [HttpPut("{id}")]
        public IActionResult PutSeckill(int id, Seckill Seckill)
        {
            if (id != Seckill.Id)
            {
                return BadRequest();
            }

            try
            {
                SeckillService.Update(Seckill);

                // 方式1
                // 1、重新下载后台数据到缓存
                // 2、异步方式

                // 方式2
                // 其他客户端更新。 如果是mysql需要借助 canal 工具。实时监控，然后canal---服务器
                //              如果是sqlserver需要借助  工具。实时监控，然后canal---服务器
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!SeckillExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }


        /// <summary>
        /// 修改秒杀商品库存
        /// </summary>
        /// <param name="seckillPo"></param>
        /// <returns></returns>
        [HttpPut("set-stock")]
        public IActionResult PutProductStock(SeckillPo seckillPo)
        {
            // 1、查询秒杀库存
            Seckill seckill = SeckillService.GetSeckillByProductId(seckillPo.ProductId);

            // 2、判断秒杀库存是否完成
            if (seckill.SeckillStock <= 0)
            {
                throw new Exception("秒杀库存完了");
            }

            // 3、扣减秒杀库存
            seckill.SeckillStock = seckill.SeckillStock - seckillPo.ProductCount;

            // 4、更新秒杀库存
            SeckillService.Update(seckill);

            // 5、seckill 转换成为Dto对象seckill 
            // SeckillDto seckillDto = AutoMapperHelper.AutoMapTo<SeckillDto>(seckill);
            return Ok("更新库存成功");
        }

        /// <summary>
        /// 异步更新秒杀库存
        /// </summary>
        /// <param name="id"></param>
        /// <param name="productVo"></param>
        /// <returns></returns>
        [NonAction]
        public IActionResult SetProductStock(SeckillPo seckillPo)
        {
            // 1、查询秒杀库存
            Seckill seckill = SeckillService.GetSeckillByProductId(seckillPo.ProductId);

            // 2、判断秒杀库存是否完成
            if (seckill.SeckillStock <= 0)
            {
                throw new Exception("秒杀库存完了");
            }

            // 3、扣减秒杀库存
            seckill.SeckillStock = seckill.SeckillStock - seckillPo.ProductCount;

            // 4、更新秒杀库存
            SeckillService.Update(seckill);
            return Ok("更新库存成功");
        }

        // POST: api/Seckills
        // To protect from overposting attacks, enable the specific properties you want to bind to, for
        // more details, see https://go.microsoft.com/fwlink/?linkid=2123754.
        [HttpPost]
        public ActionResult<Seckill> PostSeckill(Seckill Seckill)
        {
            SeckillService.Create(Seckill);
            return CreatedAtAction("GetSeckill", new { id = Seckill.Id }, Seckill);
        }

        // DELETE: api/Seckills/5
        [HttpDelete("{id}")]
        public ActionResult<Seckill> DeleteSeckill(int id)
        {
            var Seckill = SeckillService.GetSeckillById(id);
            if (Seckill == null)
            {
                return NotFound();
            }

            SeckillService.Delete(Seckill);
            return Seckill;
        }

        private bool SeckillExists(int id)
        {
            return SeckillService.SeckillExists(id);
        }


        class Customer
        {
            private string id;

            public Customer(string id)
            {
                this.id = id;
            }
        }

        class CustomerCache
        {
            private List<Customer> cache = new List<Customer>();

            public void AddCustomer(Customer c)
            {
                cache.Add(c);
            }
        }

        class Processor
        {
            private CustomerCache cache = new CustomerCache();

            public void ProcessTransaction(Customer customer)
            {
                cache.AddCustomer(customer);
            }
        }
    }
}
